package com.v1.admin.manage;

import java.util.Date;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.ErrorCode;
import org.apache.log4j.spi.LocationInfo;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;

/**
 * Log4J Appender that writes log events into the MongoDB document oriented
 * database. Log events are fully parsed and stored as structured records in
 * MongoDB (this appender does not require, nor use a Log4J layout).
 * 
 * The appender does <u>not</u> create any indexes on the data that's stored -
 * it is assumed that if query performance is required those would be created
 * externally (eg. in the mongodb shell or an external reporting application).
 * 
 * An example BSON structure for a single log entry is as follows:
 * 
 * <pre>
 * {
 *   "_id"        : ObjectId("f1c0895fd5eee04a445deb00"),
 *   "timestamp"  : "2012-05-30 17:11:12",
 *   "level"      : "ERROR",
 *   "source"     : "luyang",
 *   "message"    : "Error entry",
 *   "stackTrace" : "java.lang.NullPointerException\n\tat com.v1.cache.CategoryCache.getParents(CategoryCache.java:99)..."
 * }
 * </pre>
 */
public class MongoDBAppender extends AppenderSkeleton {

	private String hostname;
	private int port;
	private String databaseName;
	private String collectionName;
	private String user;
	private String password;

	private DBCollection collection;

	// private SimpleDateFormat sdf = new
	// SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	/**
	 * @see org.apache.log4j.Appender#requiresLayout()
	 */
	public boolean requiresLayout() {
		return (false);
	}

	/**
	 * @see org.apache.log4j.AppenderSkeleton#activateOptions()
	 */
	@Override
	public void activateOptions() {
		try {
			Mongo mongo = new Mongo(hostname, port);
			DB db = mongo.getDB(databaseName);

			if (!db.authenticate(user, password.toCharArray())) {
				throw new RuntimeException("Unable to authenticate with MongoDB server.");
			}
			collection = db.getCollection(collectionName);
		} catch (Exception e) {
			errorHandler.error("Unexpected exception while initialising MongoDBAppender.", e,
					ErrorCode.GENERIC_FAILURE);
		}
	}

	/**
	 * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent)
	 */
	@Override
	protected void append(final LoggingEvent loggingEvent) {
		DBObject bson = bsonifyLoggingEvent(loggingEvent);

		if (bson != null) {
			collection.insert(bson);
		}
	}

	/**
	 * BSONifies a single Log4J LoggingEvent object.
	 * 
	 * @param loggingEvent
	 *            The LoggingEvent object to BSONify <i>(may be null)</i>.
	 * @return The BSONified equivalent of the LoggingEvent object <i>(may be
	 *         null)</i>.
	 */
	private DBObject bsonifyLoggingEvent(final LoggingEvent loggingEvent) {
		DBObject result = null;

		if (loggingEvent != null) {
			result = new BasicDBObject();

			result.put("timestamp", new Date(loggingEvent.timeStamp));
			result.put("level", loggingEvent.getLevel().toString());
			result.put("source", user);

			String detail = "";
			LocationInfo locationInfo = loggingEvent.getLocationInformation();
			if (locationInfo != null) {
				String methodName = locationInfo.getMethodName();
				String lineNumber = locationInfo.getLineNumber();
				String className = locationInfo.getClassName();

				detail += "(" + className + "." + methodName + ":" + lineNumber + ") ";
			}

			Object message = loggingEvent.getMessage();
			if (message != null) {
				detail += message.toString();
			}
			result.put("message", detail);

			ThrowableInformation throwableInfo = loggingEvent.getThrowableInformation();
			if (throwableInfo != null) {
				String[] throwableStrRep = throwableInfo.getThrowableStrRep();
				String t = "";
				for (String s : throwableStrRep) {
					t += s + "\n";
				}
				result.put("stackTrace", t);
			}

		}

		return (result);
	}

	public void close() {
		collection = null;
	}

	public String getHostname() {
		return hostname;
	}

	public void setHostname(String hostname) {
		this.hostname = hostname;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getDatabaseName() {
		return databaseName;
	}

	public void setDatabaseName(String databaseName) {
		this.databaseName = databaseName;
	}

	public String getCollectionName() {
		return collectionName;
	}

	public void setCollectionName(String collectionName) {
		this.collectionName = collectionName;
	}

	public String getUser() {
		return user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}
